<?php
/**
* @file    idcard.php
* @version 1.2
* @author  戴剑峰
* @date    2006-08-16 最后修改时间
* @brief
*
* 验证身份证号码有效性。
*
*/
?>
<?php
/**
 * @desc 返回错误码对应错误
 *
 * @param int $errno 错误码
 * @return string 错误
 */
function getIDCardError($errno)
{
	$errors=array(
	"验证通过!",
	"身份证号码位数不对!",
	"身份证号码出生日期超出范围或含有非法字符!",
	"身份证号码校验错误!",
	"身份证地区非法!"
	);
	return $errors[$errno];
}

/**
 * @desc 身份证校验码有效性检查，不区分15位和18位
 *
 * @param string $idcard 15或18位身份证号码
 * @return int 错误码
 */
function checkIdcard($idcard)
{
	$idcard=strtolower($idcard);
	$errors=array(0,1,2,3,4);
	$area=array(
	11=>"北京",
	12=>"天津",
	13=>"河北",
	14=>"山西",
	15=>"内蒙古",
	21=>"辽宁",
	22=>"吉林",
	23=>"黑龙江",
	31=>"上海",
	32=>"江苏",
	33=>"浙江",
	34=>"安徽",
	35=>"福建",
	36=>"江西",
	37=>"山东",
	41=>"河南",
	42=>"湖北",
	43=>"湖南",
	44=>"广东",
	45=>"广西",
	46=>"海南",
	50=>"重庆",
	51=>"四川",
	52=>"贵州",
	53=>"云南",
	54=>"西藏",
	61=>"陕西",
	62=>"甘肃",
	63=>"青海",
	64=>"宁夏",
	65=>"新疆",
	71=>"台湾",
	81=>"香港",
	82=>"澳门",
	91=>"国外"
	);

	//地区检验
	if($area[intval(substr($idcard,0,2))]==null)return $errors[4];

	//身份号码位数及格式检验
	switch(strlen($idcard))
	{
		case 15:
			$year=intval(substr($idcard,6,2));
			if(($year+1900)%4==0||(($year+1900)%100==0&&($year+1900)%4==0))
			{
				$ereg='/^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$/';//测试出生日期的合法性
			}
			else
			{
				$ereg='/^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$/';//测试出生日期的合法性
			}

			if(preg_match($ereg,$idcard))return $errors[0];
			else return $errors[2];
			break;
		case 18:
			//18位身份号码检测
			//出生日期的合法性检查
			//闰年月日:((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))
			//平年月日:((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))
			$year=intval(substr($idcard,6,4));
			if($year%4==0||($year%100==0&&$year%4==0))
			{
				$ereg='/^[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}[0-9Xx]$/';//闰年出生日期的合法性正则表达式
			}
			else
			{
				$ereg='/^[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}[0-9Xx]$/';//平年出生日期的合法性正则表达式
			}
			if(preg_match($ereg,$idcard))
			{//测试出生日期的合法性
				//计算校验位
				$s=(intval($idcard{0})+intval($idcard{10}))*7+(intval($idcard{1})+intval($idcard{11}))*9+(intval($idcard{2})+intval($idcard{12}))*10+(intval($idcard{3})+intval($idcard{13}))*5+(intval($idcard{4})+intval($idcard{14}))*8+(intval($idcard{5})+intval($idcard{15}))*4+(intval($idcard{6})+intval($idcard{16}))*2+intval($idcard{7})*1+intval($idcard{8})*6+intval($idcard{9})*3;
				$y=$s%11;
				$m="F";
				$jym="10x98765432";
				$m=$jym{$y};//判断校验位
				if($m==$idcard{17})return $errors[0]; //检测ID的校验位
				else return $errors[3];
			}
			else return $errors[2];
			break;
		default:
			return $errors[1];
			break;
	}
}

/**
 * @desc 计算18位身份证号码校验码
 *
 * @param string $idcard_base 身份证前17位
 * @return char 校验码
 */
function idcard_verify_number($idcard_base)
{
	if(strlen($idcard_base)!=17){return false;}
	// 加权因子
	$factor=array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);

	// 校验码对应值
	$verify_number_list= array('1','0','X','9','8','7','6','5','4','3','2');

	$checksum=0;
	for($i=0;$i<strlen($idcard_base);$i++){
		$checksum += substr($idcard_base, $i, 1) * $factor[$i];
	}

	$mod = $checksum % 11;
	$verify_number = $verify_number_list[$mod];

	return $verify_number;
}


/**
 * @desc 将15位身份证升级到18位
 *
 * @param string $idcard 15位身份证号码
 * @return string 18位身份证号码
 */
function idcard_15to18($idcard)
{
	if (!preg_match('/^\w{15}$/',$idcard)){
		return false;
	}else{
		// 如果身份证顺序码是996 997 998 999，这些是为百岁以上老人的特殊编码
		if (array_search(substr($idcard, 12, 3), array('996', '997', '998', '999')) !== false){
			$idcard = substr($idcard, 0, 6) . '18'. substr($idcard, 6, 9);
		}else{
			$idcard = substr($idcard, 0, 6) . '19'. substr($idcard, 6, 9);
		}
	}
	$idcard = $idcard . idcard_verify_number($idcard);
	return $idcard;
}

/**
 * @desc 18位身份证有效性检查
 *
 * @param string $idcard 18位身份证号码
 * @return bool 是否通过校验
 */
function idcard_checksum18($idcard)
{
	if (!preg_match('/^\w{18}$/',$idcard)){ return false; }
	$idcard_base = substr($idcard, 0, 17);
	if (idcard_verify_number($idcard_base) != strtoupper(substr($idcard, 17, 1))){
		return false;
	}else{
		return true;
	}
}

/**
 * @desc 身份证校验码有效性检查，不区分15位和18位
 *
 * @param string $idcard 15或18位身份证号码
 * @return bool 是否通过校验
 */
function idcard_checksum($idcard)
{
	if(checkIdcard($idcard)==0)return true;
	else return false;
}

/**
 * 根据身份证判断,是否满足年龄条件
 * @param type $idcard 身份证
 * @param type $minAge 最小年龄
 */
function isMeetAgeByIDCard($idcard, $minAge) {

    if (strlen($idcard) <= 15) {
        $idcard = idcard_15to18($idcard);
    }

    $year = date('Y') - substr($idcard, 6, 4);
    $monthDay = date('md') - substr($idcard, 10, 4);

    return ($year > $minAge || ($year == $minAge && $monthDay > 0)) ? TRUE : FALSE;
}

//echo idcard_checksum18("420104197603300493");
//echo idcard_checksum("420104197603300493");
?>